package de.preamp.clients.java.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collection;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.TreeSet;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Component;

import de.preamp.clients.java.data.Artist;
import de.preamp.clients.java.data.Track;
import de.preamp.clients.java.utils.AbstractPreferences;
import de.preamp.clients.java.utils.UpdateStatus;

/**
 * Implements the {@link ServerConnection}.
 * 
 * @author André Stannek
 * @version 2010-08-22
 */
@Component("serverConnection")
public class ServerConnectionImpl implements ServerConnection
{
    /**
     * 
     */
    public ServerConnectionImpl()
    {
	Authenticator.setDefault(new Authenticator()
	{
	    @Override
	    public PasswordAuthentication getPasswordAuthentication()
	    {
		return new PasswordAuthentication(AbstractPreferences
			.instance().getServerUser(), AbstractPreferences
			.instance().getServerPassword());
	    }
	});
    }

    @Override
    public final Collection<Track> getFiles(final String keyword)
	    throws IOException, JSONException
    {
	final Collection<Track> result = new HashSet<Track>();
	final String response =
		getDBResponse("?action=get_files&keyword=" + keyword);

	parseTracks(result, response);

	return result;
    }

    @Override
    public final Collection<Track> getFilesByArtist(final String artist)
	    throws IOException, JSONException
    {
	final Collection<Track> result = new TreeSet<Track>();
	final String response =
		getDBResponse("?action=get_files_by_artist&artist=" + artist);

	parseTracks(result, response);

	return result;
    }

    @Override
    public final Collection<Track> getLibrary() throws IOException,
	    JSONException
    {
	final Collection<Track> result = new HashSet<Track>();
	final String response = getDBResponse("?action=get_library");

	parseTracks(result, response);

	return result;
    }

    @Override
    public final Track getMediaFile(final String identifier)
	    throws IOException, JSONException
    {
	final String response =
		getDBResponse("?action=get_media_file&id=" + identifier);

	final JSONObject object = new JSONObject(response);
	final JSONObject track = object.getJSONObject("result");

	return parseTrack(track);
    }

    @Override
    public void setRating(final String identifier, final short rating)
    {
	// TODO Set the rating. Server implementation must be there first.
    }

    @Override
    public final Collection<Artist> getAllArtists() throws JSONException,
	    IOException
    {
	final String response = getDBResponse("?action=get_artists");
	final TreeSet<Artist> result = new TreeSet<Artist>();

	final JSONObject object = new JSONObject(response);
	final JSONArray artists = object.getJSONArray("result");
	for (int i = 0; i < artists.length(); i++)
	{
	    final String artistString = artists.get(i).toString();
	    if (artistString.contains("/"))
	    {
		final StringTokenizer token =
			new StringTokenizer(artistString, "/");
		final HashSet<Artist> set = new HashSet<Artist>();
		while (token.hasMoreTokens())
		{
		    set.add(new Artist(token.nextToken()));
		}
		result.addAll(set);
	    }
	    else
	    {
		final Artist artist = new Artist(artistString);
		result.add(artist);
	    }
	}

	return result;
    }

    /**
     * Gets the response of the webservice to a URL request as a string.
     * 
     * @param request
     *            the request
     * @return the response
     * @throws IOException
     *             if server connection was impossible
     */
    private String getDBResponse(final String request) throws IOException
    {
	final String requestURL =
		AbstractPreferences.instance().getServerURL() + "/preamp"
			+ request.replaceAll(" ", "%20");
	String result = "";

	final URL url = new URL(requestURL);
	final URLConnection connection = url.openConnection();

	final BufferedReader inputStream =
		new BufferedReader(new InputStreamReader(
			connection.getInputStream()));

	String inputLine = inputStream.readLine();

	final StringBuilder qResult = new StringBuilder();
	while (inputLine != null)
	{
	    qResult.append(inputLine);
	    inputLine = inputStream.readLine();
	}
	result = qResult.toString();

	inputStream.close();

	return result;

    }

    /**
     * parses and returns a single {@link Track} object from a
     * {@link JSONObject}.
     * 
     * @param track
     *            the {@link JSONObject} describing the track
     * @return the {@link Track} object built from the {@link JSONObject}.
     * @throws JSONException
     *             if given {@link JSONObject} is not a valid track object
     *             maintaining the preamp server specifications.
     * @throws MalformedURLException
     *             if link field in track {@link JSONObject} doesn't contain a
     *             valid url.
     */
    private Track parseTrack(final JSONObject track) throws JSONException,
	    MalformedURLException
    {
	final String dbID = track.getString("id");
	final String artist = track.getString("artist");
	final String title = track.getString("title");
	final String album = track.getString("album");
	final String link = track.getString("link");
	final int length = Integer.parseInt(track.getString("length"));
	final int trackNo = track.getInt("track");
	final short rating = 0;
	// short rating = Short.parseShort(track.getString("rating"));
	return new Track(dbID, artist, title, album, trackNo, rating, length,
		link);
    }

    /**
     * Parses all tracks contained in a database response and adds them to a
     * collection.
     * 
     * @param tracks
     *            the collection the tracks are added to.
     * @param response
     *            the response from the database.
     * @throws JSONException
     *             if response is not a json-object maintaining the preamp
     *             server interface specifications.
     * @throws MalformedURLException
     *             if URL for a track is malformed
     */
    private void parseTracks(final Collection<Track> tracks,
	    final String response) throws JSONException, MalformedURLException
    {
	final JSONObject obj = new JSONObject(response);
	final JSONArray jsonTracks = obj.getJSONArray("result");
	JSONObject track = null;
	for (int i = 0; i < jsonTracks.length(); i++)
	{
	    track = jsonTracks.getJSONObject(i);

	    tracks.add(parseTrack(track));
	}
    }

    @Override
    public final boolean startUpdate() throws IOException, JSONException
    {
	final String response = getDBResponse("?action=update_db");
	final JSONObject object = new JSONObject(response);
	return "ok".equals(object.getString("status"));
    }

    @Override
    public final UpdateStatus updateStatus() throws IOException, JSONException
    {
	final String response = getDBResponse("?action=update_db_status");
	final JSONObject object =
		new JSONObject(response).getJSONObject("update_status");
	return new UpdateStatus(object.getInt("new_added"),
		object.getInt("deleted_removed"),
		object.getInt("total_number_of_files"),
		object.getString("progress"));
    }
}
